<!DOCTYPE html>
<!-- saved from url=(0038)https://imququ.com/post/web-proxy.html -->
<html lang="zh-CN">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
		<meta name="format-detection" content="telephone=no">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<title>HTTP 代理原理及实现（一） | JerryQu 的小站</title>
		<meta name="theme-color" content="#007b8b">
		<meta name="mobile-web-app-capable" content="yes">
		<meta name="application-name" content="JerryQu 的小站">
		<meta name="msapplication-starturl" content="https://imququ.com">
		<meta name="msapplication-navbutton-color" content="#007b8b">
		<meta name="apple-mobile-web-app-capable" content="yes">
		<meta name="apple-mobile-web-app-title" content="JerryQu 的小站">
		<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
		<link rel="apple-touch-icon" href="https://st.imququ.com/static/img/blog/icon.png">
		<link rel="manifest" href="https://imququ.com/static/manifest.json">
		<link rel="search" type="application/opensearchdescription+xml" href="https://imququ.com/opensearch.xml"
			title="JerryQu 的小站">
		<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="https://imququ.com/rss.html">
		<style>
			* {
				margin: 0;
				padding: 0
			}

			html,
			body {
				height: 100%
			}

			body {
				background: #ddd;
				color: #666;
				font-size: 14px;
				font-family: Roboto, Helvetica, Tahoma, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK", "WenQuanYi Micro Hei", sans-serif;
				-webkit-font-smoothing: antialiased
			}

			::selection,
			::-moz-selection,
			::-webkit-selection {
				background-color: #2479CC;
				color: #eee
			}

			h1 {
				font-size: 2em
			}

			h3 {
				font-size: 1.3em
			}

			h4 {
				font-size: 1.1em
			}

			a {
				text-decoration: none;
				color: #076dd0
			}

			article {
				border-bottom: 1px solid #ddd;
				border-top: 1px solid #fff;
				position: relative;
				padding: 30px 0
			}

			.container {
				position: relative;
				min-height: 100%;
				max-width: 1600px
			}

			.left-col {
				width: 270px;
				position: fixed;
				height: 100%;
				background-color: #007b8b;
				background-image: url(https://st.imququ.com/static/img/blog/left-bg2.jpg);
				background-size: cover
			}

			.mid-col {
				position: absolute;
				left: 0;
				right: 0;
				min-height: 100%;
				margin-left: 270px;
				background: #fff
			}

			article .meta {
				position: relative;
				float: right;
				color: #555;
				font-size: .9em;
				line-height: 2;
				width: auto;
				text-align: right
			}

			article .entry-content,
			article .meta a {
				color: #444
			}

			article .entry-content {
				font-size: 16px;
				line-height: 1.8;
				word-wrap: break-word
			}

			article h1.title {
				color: #333;
				font-size: 2em;
				font-weight: 400;
				line-height: 35px;
				margin-bottom: 25px
			}

			article .entry-content p {
				margin-top: 15px
			}

			article h1.title a {
				color: #333;
				transition: color .3s
			}

			.mid-col .mid-col-container {
				padding: 0 70px 0 40px
			}

			article .meta .date,
			article .meta .comment,
			article .meta .tags {
				position: relative
			}

			article .meta .date-modified {
				display: none
			}

			article .entry-content a:hover {
				text-decoration: underline
			}

			article h1.title a:hover {
				color: #076dd0
			}

			#header {
				width: 100%;
				line-height: 30px;
				height: auto;
				border-bottom: none;
				padding: 30px 0;
				margin-left: 50px
			}

			#main-nav,
			#sub-nav {
				float: none;
				margin-top: 15px
			}

			#main-nav {
				margin-left: 0
			}

			#sub-nav {
				position: relative
			}

			#content {
				width: 100%;
				margin: 0 auto
			}

			#footer {
				line-height: 2.2;
				text-align: center;
				width: auto;
				padding: 15px 70px 15px 40px;
				border-top: 1px solid #fff;
				font-size: .9em
			}

			#header a {
				color: #fff;
				text-shadow: 0 1px #666;
				transition: color .3s
			}

			#header h1 {
				font-weight: 400;
				font-size: 30px;
				float: none
			}

			#main-nav ul li {
				margin-left: 0;
				display: block;
				position: relative
			}

			#header .subtitle {
				color: #fff
			}

			#sub-nav .social {
				margin-bottom: 10px
			}

			#footer .beian {
				color: #666
			}

			#header a:hover {
				color: #ccc
			}

			#header .profilepic a {
				display: block;
				width: 160px;
				height: 160px;
				border-radius: 50%;
				background-image: url();
				background-size: 100% 100%;
				margin: 15px 0 20px -10px
			}

			#sub-nav .social a {
				border-radius: 50%;
				display: inline-block;
				vertical-align: middle;
				text-indent: -9999px;
				margin: 0 6px 15px;
				width: 28px;
				height: 28px;
				background-size: 20px 20px;
				background-position: center center;
				background-repeat: no-repeat;
				opacity: .75;
				transition: opacity .3s
			}

			#sub-nav .social a:hover {
				opacity: 1
			}

			#sub-nav .social a:first-of-type {
				margin-left: 0
			}

			#sub-nav .social a:last-of-type {
				margin-right: 0
			}

			#sub-nav .social a.twitter {
				background-image: url();
				background-color: #55cff8;
				border: 1px solid #55CFF8
			}

			#sub-nav .social a.search {
				background-image: url();
				background-color: #afb6ca;
				border: 1px solid #afb6ca
			}

			#sub-nav .social a.rss {
				background-image: url();
				background-color: #ef7522;
				border: 1px solid #ef7522
			}

			#sub-nav .social a.twitter:hover {
				border: 1px solid #24c1f6
			}

			#sub-nav .social a.search:hover {
				border: 1px solid #909ab6
			}

			#sub-nav .social a.rss:hover {
				border: 1px solid #cf5d0f
			}

			@media screen and (max-width:1024px) {
				article {
					padding-bottom: 15px
				}

				.left-col {
					width: 210px
				}

				.mid-col {
					margin-left: 210px
				}

				article h1.title,
				article .entry-content {
					margin-left: 0
				}

				.mid-col .mid-col-container {
					padding: 0 20px
				}

				#header {
					margin-left: 30px
				}

				#footer {
					padding: 15px 20px
				}
			}

			@media screen and (max-width:640px) {
				article {
					padding: 20px 0
				}

				.left-col {
					position: relative;
					width: 100%;
					background-image: none
				}

				.mid-col {
					margin-left: 0;
					float: none;
					width: 100%
				}

				article .meta {
					margin-bottom: 10px;
					position: static;
					width: auto
				}

				.mid-col .mid-col-container {
					padding: 0 10px
				}

				.total_thread .tips {
					display: block;
					margin-top: 10px
				}

				.mid-col article .meta {
					float: none;
					overflow: hidden
				}

				article .meta .date,
				article .meta .comment,
				article .meta .tags {
					display: inline;
					margin-right: 5px;
					padding-left: 0
				}

				article .meta .date {
					margin-right: 30px
				}

				#header {
					padding: 20px 0;
					margin-left: 0;
					text-align: center
				}

				#main-nav {
					margin-top: 10px
				}

				#footer {
					padding: 15px 10px
				}

				#main-nav ul li {
					display: inline;
					margin: 0 10px;
					text-align: center
				}

				#header .profilepic a {
					position: absolute;
					top: 12px;
					left: 12px;
					width: 56px;
					height: 56px;
					margin: 0
				}

				#sub-nav .social,
				#sub-nav .social a {
					margin-bottom: 0
				}

				#sub-nav .social a {
					opacity: 1
				}

				#content #toc-container,
				#content #toc {
					float: none
				}

				#content #toc {
					max-width: 100%;
					margin: 0
				}
			}

			article input.runcode,
			article button {
				line-height: 1;
				border: none;
				cursor: pointer;
				padding: .625em .5em;
				border-radius: 0;
				box-shadow: inset 0 -5px 20px rgba(0, 0, 0, .1);
				background: #12b0e6;
				color: #fff;
				font-size: 14px;
				margin-top: 10px;
				-webkit-appearance: none
			}

			article button {
				margin-top: 0
			}

			article strong {
				font-weight: 700
			}

			article em {
				font-style: italic
			}

			article blockquote {
				margin-top: 10px;
				padding: 15px 20px;
				border-left: 5px solid #2479CC;
				background-color: #f8f8f8;
				overflow: hidden
			}

			article code {
				font-family: Consolas, Monaco, 'Andale Mono', monospace;
				font-size: 80%;
				margin: 0 2px;
				padding: 4px 5px;
				background-color: #eee;
				vertical-align: middle;
				border-radius: 5px
			}

			article pre {
				font-size: 14px;
				line-height: 1.6;
				color: #5d6a6a;
				padding: .6em;
				border-left: 5px solid #ccc;
				background-color: #f8f8f8;
				word-break: break-word;
				word-wrap: break-word;
				white-space: pre-wrap;
				position: relative;
				overflow: hidden
			}

			article img {
				padding: 0;
				display: block;
				border: 1px solid #ccc;
				margin: 10px 0 5px 0;
				max-width: 100%
			}

			article table {
				border: 0;
				border-collapse: collapse;
				border-spacing: 0
			}

			article pre code {
				border-radius: 0 0 0 0;
				background-color: transparent;
				border: 0;
				padding: 0;
				margin: 0;
				font-size: 100%;
				display: block;
				position: relative
			}

			article table th,
			article table td {
				border: 0
			}

			article table th {
				border-bottom: 2px solid #848484;
				text-align: left;
				padding: 6px 20px
			}

			article table td {
				border-bottom: 1px solid #d0d0d0;
				padding: 6px 20px
			}

			.total_thread {
				line-height: 1.6
			}

			.page-navi {
				width: 100%;
				line-height: 20px;
				position: relative;
				border-top: 1px solid #fff;
				border-bottom: 1px solid #ddd;
				padding: 20px 0;
				overflow: hidden
			}

			article .copyright-info {
				font-size: 14px
			}

			article .expired-tips {
				padding: 5px 10px;
				font-size: 15px;
				border: 1px solid #e2e2e2;
				background-color: #ffffc0;
				border-left: 5px solid #fff000;
				color: #333;
				overflow: hidden
			}

			article .post-info {
				font-size: 14px
			}

			article.post-search {
				padding-bottom: 0
			}

			article input.runcode:hover,
			article input.runcode:focus,
			article input.runcode:active,
			article button:hover,
			article button:focus,
			article button:active {
				background: #f6ad08
			}

			article img.loaded {
				height: auto !important
			}

			article .entry-content blockquote,
			article .entry-content ul,
			article .entry-content ol,
			article .entry-content dl,
			article .entry-content table,
			article .entry-content iframe,
			article .entry-content h1,
			article .entry-content h2,
			article .entry-content h3,
			article .entry-content h4,
			article .entry-content h5,
			article .entry-content h6,
			article .entry-content pre {
				margin-top: 15px
			}

			article .entry-content ul,
			article .entry-content ol,
			article .entry-content dl {
				margin-left: 25px
			}

			article pre b.name {
				font-family: "Consolas", "Liberation Mono", Courier, monospace;
				position: absolute;
				line-height: 1;
				top: 10px;
				right: 10px;
				font-size: 60px;
				color: #eee;
				pointer-events: none
			}

			article .entry-content .date {
				color: #666;
				font-size: 14px
			}

			article .entry-content ul ul,
			article .entry-content ul ol,
			article .entry-content ul dl,
			article .entry-content ol ul,
			article .entry-content ol ol,
			article .entry-content ol dl,
			article .entry-content dl ul,
			article .entry-content dl ol,
			article .entry-content dl dl,
			article .entry-content blockquote>p:first-of-type {
				margin-top: 0
			}

			#comments {
				border-top: 1px solid #fff;
				border-bottom: 1px solid #ddd;
				padding: 20px 0;
				min-height: 350px
			}

			#comments h1.title {
				font-size: 25px;
				font-weight: 400;
				line-height: 35px;
				margin-bottom: 10px
			}

			.total_thread .tips {
				color: #888
			}

			.page-navi .prev {
				float: left
			}

			.page-navi .next {
				float: right
			}

			.page-navi .center {
				text-align: center;
				width: 80px;
				margin: auto
			}

			#searchResult {
				min-height: 350px
			}

			#search form {
				position: relative
			}

			#search .wrapper {
				margin-right: 72px
			}

			#search .submit {
				-webkit-appearance: none;
				background-color: #e7e7e7;
				border: 1px solid #bbb;
				border-left: 0;
				border-radius: 0;
				color: #222;
				display: block;
				font-size: 16px;
				height: 40px;
				outline: 0;
				position: absolute;
				right: 0;
				top: 0;
				width: 72px
			}

			#searchResult .info {
				color: #676767;
				font-size: 13px;
				padding: 15px 0;
				border-bottom: 1px solid #ddd
			}

			#searchResult .no-result {
				padding: 5px;
				margin: 15px 0;
				border: 1px solid #fc3;
				background-color: #fff4c2;
				font-size: 13px
			}

			#searchResult .loading,
			#searchResult .hot-words {
				margin-top: 20px
			}

			#searchResult .item {
				padding: 10px 0 20px;
				border-bottom: 1px solid #ddd
			}

			#search .wrapper input {
				-webkit-appearance: none;
				border: 1px solid #bbb;
				border-radius: 0;
				box-sizing: border-box;
				display: block;
				font-size: 16px;
				height: 40px;
				outline: 0;
				padding: 4px 6px;
				width: 100%;
				background-color: #fff
			}

			#searchResult .hot-words a {
				margin-right: 20px
			}

			#searchResult .item:last-of-type {
				border-bottom: 0
			}

			#searchResult .item .title {
				margin: 5px 0
			}

			#searchResult .item .desc {
				font-size: 14px;
				overflow: hidden
			}

			#searchResult .item .img {
				width: 108px;
				height: 81px;
				border: 1px solid #ccc;
				float: left;
				margin: 4px 8px 0 0
			}

			#searchResult .item .title a {
				font-size: 17px
			}

			#searchResult .item .img img {
				width: 100%;
				border: 0;
				margin: 0
			}

			#searchResult .item .title .type {
				display: inline-block;
				background-color: #eee;
				color: #888;
				margin-right: 6px;
				padding: 0 4px;
				font-size: 12px;
				border-radius: 3px;
				position: relative;
				top: -1px
			}

			.hljs-comment,
			.diff .hljs-header,
			.hljs-doctype,
			.hljs-pi,
			.lisp .hljs-string,
			.hljs-javadoc {
				color: #999
			}

			.hljs-keyword,
			.hljs-winutils,
			.method,
			.hljs-addition,
			.css .hljs-tag,
			.hljs-request,
			.hljs-status,
			.nginx .hljs-title {
				color: #859900
			}

			.hljs-number,
			.hljs-command,
			.hljs-string,
			.hljs-tag .hljs-value,
			.hljs-rule .hljs-value,
			.hljs-phpdoc,
			.hljs-dartdoc,
			.tex .hljs-formula,
			.hljs-regexp,
			.hljs-hexcolor,
			.hljs-link_url {
				color: #2aa198
			}

			.hljs-title,
			.hljs-localvars,
			.hljs-chunk,
			.hljs-decorator,
			.hljs-built_in,
			.hljs-identifier,
			.vhdl .hljs-literal,
			.hljs-id,
			.css .hljs-function,
			.hljs-name {
				color: #268bd2
			}

			.hljs-attribute,
			.hljs-variable,
			.lisp .hljs-body,
			.smalltalk .hljs-number,
			.hljs-constant,
			.hljs-class .hljs-title,
			.hljs-parent,
			.hljs-type,
			.hljs-link_reference {
				color: #b58900
			}

			.hljs-preprocessor,
			.hljs-preprocessor .hljs-keyword,
			.hljs-pragma,
			.hljs-shebang,
			.hljs-symbol,
			.hljs-symbol .hljs-string,
			.diff .hljs-change,
			.hljs-special,
			.hljs-attr_selector,
			.hljs-subst,
			.hljs-cdata,
			.css .hljs-pseudo,
			.hljs-header {
				color: #cb4b16
			}

			.hljs-deletion,
			.hljs-important {
				color: #dc322f
			}

			.hljs-link_label {
				color: #6c71c4
			}

			.tex .hljs-formula {
				background: #eee8d5
			}

			#toc-container,
			#toc {
				float: right
			}

			#toc {
				min-width: 120px;
				max-width: 260px;
				padding: 6px;
				margin: 0 0 15px 20px;
				border: 1px solid #e2e2e2;
				font-size: 14px
			}

			#toc strong {
				border-bottom: 1px solid #e2e2e2;
				display: block
			}

			#toc p {
				padding: 0 4px;
				margin: 0
			}

			#toc ul {
				margin: .5em .5em .5em 1.5em
			}

			#simple_thread ol,
			#simple_thread ul {
				list-style: none;
				list-style-type: none
			}

			#toc ul ul {
				margin-top: 0;
				margin-bottom: 0
			}

			#simple_thread>.thread {
				font-family: "Helvetica Neue", arial, sans-serif;
				-webkit-font-smoothing: antialiased;
				-moz-osx-font-smoothing: grayscale;
				color: #2a2e2e
			}

			#simple_thread .thread-new {
				margin-bottom: 20px
			}

			#simple_thread .avatar {
				float: left
			}

			#simple_thread .nav {
				position: relative;
				margin: 0 0 20px;
				border-bottom: 2px solid #e7e9ee
			}

			#simple_thread .tab-conversation {
				float: left
			}

			#simple_thread .btn {
				border: none;
				background: #778289;
				background: rgba(29, 47, 58, .6);
				display: inline-block;
				padding: 10px 16px;
				color: #fff;
				line-height: 1.1;
				border-radius: 3px;
				font-weight: 500;
				transition: background .2s;
				text-shadow: none
			}

			#simple_thread .post-content {
				position: relative;
				margin-bottom: 24px;
				transition: all .2s ease-in-out
			}

			#simple_thread .post-body {
				overflow: hidden
			}

			#simple_thread .clearfix:after,
			#simple_thread .clearfix:before {
				display: table;
				content: "";
				line-height: 0;
				clear: both
			}

			#simple_thread .publisher-anchor-color a {
				color: #2479cc !important
			}

			#searchResult .item .summary {
				display: block;
				line-height: 22px;
				max-height: 88px
			}

			#simple_thread .thread-new .create-post {
				line-height: 1;
				border: none;
				cursor: pointer;
				padding: .625em .5em;
				border-radius: 0;
				box-shadow: inset 0 -5px 20px rgba(0, 0, 0, .1);
				background: #12b0e6;
				color: #fff;
				font-size: 14px;
				-webkit-appearance: none;
				margin-right: 10px
			}

			#simple_thread .avatar .user,
			#simple_thread .avatar img {
				display: block;
				border-radius: 3px
			}

			#searchResult .item .title b,
			#searchResult .item .desc b {
				color: #C00;
				font-weight: 400
			}

			#simple_thread .active .publisher-nav-color::after {
				background-color: #076dd0 !important
			}

			#simple_thread .avatar img {
				width: 48px;
				height: 48px
			}

			#simple_thread .avatar .user {
				position: relative;
				z-index: 100;
				background: #dbdfe4;
				padding: 0
			}

			#simple_thread .post .avatar {
				margin-right: 12px
			}

			#simple_thread .nav-tab>a {
				color: #7f919e;
				display: block;
				margin: 0;
				padding: 0;
				font-weight: 700;
				line-height: 1;
				position: relative;
				transition: all .2s ease-in-out
			}

			#simple_thread .tab-conversation>a {
				text-transform: capitalize;
				font-size: 15px;
				padding: 12px 0;
				margin-right: 15px
			}

			#simple_thread .btn:hover {
				background: #606d75;
				background: rgba(29, 47, 58, .7);
				color: #fff
			}

			#simple_thread .btn.active,
			#simple_thread .btn:active {
				transition: none;
				background: #2e9fff
			}

			#simple_thread .btn.busy {
				background: #ebeef2;
				color: #999;
				text-shadow: none
			}

			#simple_thread .children .show-children-wrapper {
				margin-bottom: 24px
			}

			#simple_thread .children .show-children {
				margin-left: 60px;
				font-size: 13px;
				line-height: 15px;
				color: #656c7a;
				text-decoration: underline
			}

			#simple_thread .post-content .indicator {
				position: absolute;
				top: 0;
				left: 0;
				width: 5px;
				height: 48px;
				border-radius: 3px
			}

			#simple_thread .post-content.target {
				padding-left: 12px
			}

			#simple_thread .tab-conversation.active>a {
				color: #2a2e2e !important
			}

			#simple_thread .post-body a[data-dsq-mention] {
				font-weight: 700
			}

			#simple_thread .tab-conversation.active>a:after {
				content: " ";
				display: block;
				height: 2px;
				background: #2e9fff;
				position: absolute;
				bottom: -2px;
				left: 0;
				right: 0
			}

			#simple_thread .post-body a[data-dsq-mention]:before {
				content: '@'
			}

			#simple_thread .btn.busy:active,
			#simple_thread .btn.busy:hover {
				background: #ebeef2;
				text-shadow: none;
				cursor: not-allowed
			}

			#simple_thread .post-content.target>.avatar {
				left: 12px
			}

			#simple_thread .post-content.target .indicator {
				background: #ffc62e
			}

			#simple_thread .post-header {
				line-height: 1;
				font-size: 13px;
				padding-right: 46px;
				margin-bottom: 3px
			}

			#simple_thread .post-meta {
				display: inline-block
			}

			#simple_thread .load-more {
				margin: 0 0 24px
			}

			#simple_thread .post-body spoiler {
				display: inline;
				background: #7f919e;
				color: transparent;
				padding: 0 .5em
			}

			#simple_thread .post-header a {
				color: #7f919e;
				line-height: 1
			}

			#simple_thread .post-body-inner p {
				font-size: 15px;
				line-height: 21px;
				margin: 0 0 15px;
				word-wrap: break-word
			}

			#simple_thread .media a {
				margin-right: 10px
			}

			#simple_thread .post-header .author {
				color: #656c7a;
				font-weight: 700
			}

			#simple_thread .post-header .time-ago,
			#simple_thread .post-header .reply {
				font-weight: 500;
				font-size: 12px;
				color: #656c7a
			}

			#simple_thread .post-meta .bullet {
				padding: 0;
				color: #c2c6cc;
				line-height: 1.4
			}

			#simple_thread .children .post {
				margin-left: 60px
			}

			#simple_thread .load-more .btn {
				font-size: 13px;
				font-weight: 400;
				display: block;
				text-align: center;
				padding: 11px 14px
			}

			#simple_thread.mobile .post-header {
				padding-right: 0;
				margin-bottom: 4px;
				font-size: 14px;
				line-height: 18px;
				position: relative;
				top: -4px
			}

			#simple_thread.mobile .post-body-inner {
				clear: left;
				position: relative;
				top: -4px;
				overflow: visible
			}

			#simple_thread.mobile .post-body {
				display: block;
				overflow: visible
			}

			#simple_thread .post-body spoiler:hover,
			#simple_thread .post-body spoiler:focus {
				background: #e7e9ee;
				color: inherit
			}

			#simple_thread .post-body-inner p:last-child {
				margin: 0
			}

			#simple_thread.mobile .avatar img {
				width: 30px;
				height: 30px
			}

			#simple_thread .post-header .time-ago:hover,
			#simple_thread .post-header .reply:hover {
				color: #2a2e2e
			}

			#simple_thread .children .post .indicator {
				height: 36px
			}

			#simple_thread .children .children .post,
			#simple_thread .children .children .show-children {
				margin-left: 48px
			}

			#simple_thread.mobile .post-header .post-byline {
				display: block;
				overflow: hidden;
				text-overflow: ellipsis;
				white-space: nowrap;
				padding-right: 15px
			}

			#simple_thread.mobile .post-header .author {
				font-size: 15px;
				line-height: 18px
			}

			#simple_thread.mobile .post-meta .bullet-first {
				display: none
			}

			#simple_thread.mobile .children .show-children {
				margin-left: 0
			}

			#simple_thread.mobile .tab-conversation.active>a>span {
				display: none
			}

			#simple_thread .post.minimized>.post-content .post-body-inner {
				opacity: .7;
				font-size: 12px;
				line-height: 36px
			}

			#simple_thread .post.minimized>.post-content .post-header {
				display: none
			}

			#simple_thread.mobile .post-list .post .post-content {
				margin-bottom: 16px;
				transition: none
			}

			#simple_thread.mobile .post-list .post .children,
			#simple_thread.mobile .children .children .show-children {
				padding-left: 17px;
				border-left: 2px solid #e7e9ee
			}

			#simple_thread .post.minimized>.post-content .avatar img,
			#simple_thread .children .post .avatar .user img {
				width: 36px;
				height: 36px
			}

			#simple_thread .post.minimized>.post-content .avatar img {
				border-radius: 2px
			}

			#simple_thread .children .children .children .children .post {
				margin-left: 0
			}

			#simple_thread.mobile .post-list .post .post-content .indicator {
				height: 30px
			}

			#simple_thread.mobile .post-list .post .children .post {
				margin-left: 0
			}

			#simple_thread.mobile .post-list .post.minimized>.post-content {
				margin-bottom: 12px
			}

			#simple_thread.mobile .post-list .post .post-content .avatar img {
				width: 30px;
				height: 30px
			}

			#simple_thread.mobile .post-list .post .children li:only-child {
				margin-bottom: 20px
			}

			#simple_thread .children .children .children .children .post .post-body {
				margin-left: 48px
			}

			#simple_thread .children .children .children .children .post .indicator {
				left: 0
			}

			#simple_thread.mobile .post-list .post .post-content.target .avatar {
				margin-left: 8px
			}

			#simple_thread.mobile .post-list .post .children .children .children {
				padding-left: 0;
				border-left: none
			}

			#simple_thread.mobile .post-list .post.minimized>.post-content .post-body-inner {
				clear: none
			}

			#simple_thread.mobile .post-list .post .children .post .post-content .post-body {
				margin-left: 0
			}
		</style>
		<link rel="amphtml" href="https://imququ.com/amp/post/web-proxy.html">
		<meta name="description"
			content="今天这篇文章，我打算谈谈 HTTP 代理本身的一些原理（例如什么是代理，什么是隧道代理，什么是正向代理，什么是反向代理，CONNECT 请求是用来干嘛的），以及如何用 Node.js 快速实现代理。">
		<meta name="twitter:card" content="summary">
		<meta name="twitter:site" content="@qgy18">
		<meta name="twitter:title" content="HTTP 代理原理及实现（一） | JerryQu 的小站">
		<meta name="twitter:description"
			content="今天这篇文章，我打算谈谈 HTTP 代理本身的一些原理（例如什么是代理，什么是隧道代理，什么是正向代理，什么是反向代理，CONNECT 请求是用来干嘛的），以及如何用 Node.js 快速实现代理。">
		<meta name="twitter:image" content="https://st.imququ.com/static/img/blog/ququ_avatar.jpg">

	</head>
	<body>
		<div class="container">
			<div class="left-col">
				<div class="intrude-less">
					<header id="header" class="inner">
						<div class="profilepic"> <a href="https://imququ.com/" aria-label="Home"></a></div>
						<h1><a href="https://imququ.com/">Jerry Qu</a></h1>
						<p class="subtitle"> 专注 WEB 端开发</p>
						<nav id="main-nav">
							<ul>
								<li><a href="https://imququ.com/"><span>首页</span></a> </li>
								<li><a href="https://imququ.com/post/series.html"><span>专题</span></a> </li>
								<li><a href="https://imququ.com/post/archives.html"><span>归档</span></a> </li>
								<li><a href="https://imququ.com/post/blogroll.html"><span>友链</span></a> </li>
								<li><a href="https://imququ.com/post/about.html"><span>关于</span></a> </li>
							</ul>
						</nav>
						<nav id="sub-nav">
							<div class="social"> <a class="twitter external" rel="nofollow"
									href="https://twitter.com/qgy18" title="Twitter" aria-label="Twitter">Twitter</a><a
									class="rss" href="https://imququ.com/rss.html" title="RSS 订阅"
									aria-label="RSS 订阅">RSS</a><a class="search" href="https://imququ.com/search.html"
									title="站内搜索" aria-label="站内搜索">Search</a></div>
						</nav>
					</header>
				</div>
			</div>
			<div class="mid-col">
				<div class="mid-col-container">
					<div id="content" class="inner"><s> </s>
						<script type="application/ld+json">
							{
								"@context": "http://schema.org",
								"@type": "BlogPosting",
								"mainEntityOfPage": "https://imququ.com/post/web-proxy.html",
								"headline": "HTTP 代理原理及实现（一）",
								"datePublished": "2015-11-20T00:45:24+08:00",
								"dateModified": "2015-11-20T00:45:24+08:00",
								"author": {
									"@type": "Person",
									"name": "JerryQu"
								},
								"publisher": {
									"@type": "Organization",
									"name": "JerryQu 的小站",
									"logo": {
										"@type": "ImageObject",
										"url": "https://st.imququ.com/static/img/blog/banner.png",
										"width": 600,
										"height": 60
									}
								},
								"image": {
									"@type": "ImageObject",
									"url": "https://imququ.com/i/resize/static/uploads/2015/11/web_proxy.png",
									"width": 1200,
									"height": 800
								}
							}
						</script>
						<article class="post post-213">
							<div class="meta">
								<div class="date"> <time>Nov 20, 2015</time></div>
								<div class="date-modified"> <time>Nov 20, 2015</time></div>
								<div class="comment"> <a href="https://imququ.com/post/web-proxy.html#comments">54
										Comments</a></div>
							</div>
							<h1 class="title">HTTP 代理原理及实现（一）</h1>
							<div class="entry-content">
								<div id="toc-container">
									<div id="toc">
										<p> <strong>文章目录</strong></p>
										<ul>
											<li> <a href="https://imququ.com/post/web-proxy.html#toc-0">普通代理</a> </li>
											<li> <a href="https://imququ.com/post/web-proxy.html#toc-1">隧道代理</a> </li>
										</ul>
									</div>
								</div>
								<p class="expired-tips"> 提醒：本文最后更新于&nbsp;2403&nbsp;天前，文中所描述的信息可能已发生改变，请谨慎使用。</p>
								<p>Web 代理是一种存在于网络中间的实体，提供各式各样的功能。现代网络系统中，Web 代理无处不在。我之前有关 HTTP 的博文中，多次提到了代理对 HTTP
									请求及响应的影响。今天这篇文章，我打算谈谈 HTTP 代理本身的一些原理，以及如何用 Node.js 快速实现代理。</p>
								<p>HTTP 代理存在两种形式，分别简单介绍如下：</p>
								<p>第一种是 <a href="http://tools.ietf.org/html/rfc7230">RFC 7230 - HTTP/1.1: Message Syntax
										and Routing</a>（即修订后的 RFC 2616，HTTP/1.1
									协议的第一部分）描述的普通代理。这种代理扮演的是「中间人」角色，对于连接到它的客户端来说，它是服务端；对于要连接的服务端来说，它是客户端。它就负责在两端之间来回传送
									HTTP 报文。</p>
								<p>第二种是 <a
										href="https://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01">Tunneling
										TCP based protocols through Web proxy servers</a>（通过 Web 代理服务器用隧道方式传输基于 TCP
									的协议）描述的隧道代理。它通过 HTTP 协议正文部分（Body）完成通讯，以 HTTP 的方式实现任意基于 TCP 的应用层协议代理。这种代理使用 HTTP 的
									CONNECT 方法建立连接，但 CONNECT 最开始并不是 RFC 2616 - HTTP/1.1 的一部分，直到 2014 年发布的 HTTP/1.1
									修订版中，才增加了对 CONNECT 及隧道代理的描述，详见 <a
										href="https://tools.ietf.org/html/rfc7231#section-4.3.6">RFC 7231 - HTTP/1.1:
										Semantics and Content</a>。实际上这种代理早就被广泛实现。</p>
								<p>本文描述的第一种代理，对应《HTTP 权威指南》一书中第六章「代理」；第二种代理，对应第八章「集成点：网关、隧道及中继」中的 8.5 小节「隧道」。</p>
								<h3 id="toc-0">普通代理</h3>
								<p>第一种 Web 代理原理特别简单：</p>
								<blockquote>
									<p>HTTP 客户端向代理发送请求报文，代理服务器需要正确地处理请求和连接（例如正确处理 Connection:
										keep-alive），同时向服务器发送请求，并将收到的响应转发给客户端。</p>
								</blockquote>
								<p>下面这张图片来自于《HTTP 权威指南》，直观地展示了上述行为：</p>
								<p><img alt="web_proxy" itemprop="image" width="650" height="300"
										src="./HTTP 代理原理及实现（一） _ JerryQu 的小站_files/web_proxy.png.webp" class="loaded">
								</p>
								<p>假如我通过代理访问 A 网站，对于 A 来说，它会把代理当做客户端，完全察觉不到真正客户端的存在，这实现了隐藏客户端 IP 的目的。当然代理也可以修改 HTTP
									请求头部，通过 <code>X-Forwarded-IP</code> 这样的自定义头部告诉服务端真正的客户端
									IP。但服务器无法验证这个自定义头部真的是由代理添加，还是客户端修改了请求头，所以从 HTTP 头部字段获取 IP 时，需要格外小心。这部分内容可以参考我之前的《<a
										href="https://imququ.com/post/x-forwarded-for-header-in-http.html">HTTP 请求头中的
										X-Forwarded-For</a>》这篇文章。</p>
								<p>给浏览器显式的指定代理，需要手动修改浏览器或操作系统相关设置，或者指定 PAC（Proxy
									Auto-Configuration，自动配置代理）文件自动设置，还有些浏览器支持 WPAD（Web Proxy Autodiscovery Protocol，Web
									代理自动发现协议）。显式指定浏览器代理这种方式一般称之为正向代理，浏览器启用正向代理后，会对 HTTP
									请求报文做一些修改，来规避老旧代理服务器的一些问题，这部分内容可以参考我之前的《<a
										href="https://imququ.com/post/the-proxy-connection-header-in-http-request.html">Http
										请求头中的 Proxy-Connection</a>》这篇文章。</p>
								<p>还有一种情况是访问 A
									网站时，实际上访问的是代理，代理收到请求报文后，再向真正提供服务的服务器发起请求，并将响应转发给浏览器。这种情况一般被称之为反向代理，它可以用来隐藏服务器 IP
									及端口。一般使用反向代理后，需要通过修改 DNS 让域名解析到代理服务器 IP，这时浏览器无法察觉到真正服务器的存在，当然也就不需要修改配置了。反向代理是 Web
									系统最为常见的一种部署方式，例如本博客就是使用 Nginx 的 <code>proxy_pass</code> 功能将浏览器请求转发到背后的 Node.js 服务。
								</p>
								<p>了解完第一种代理的基本原理后，我们用 Node.js 实现一下它。只包含核心逻辑的代码如下：</p>
								<pre><b class="name">JS</b><code class="lang-js"><span class="hljs-keyword">var</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);
<span class="hljs-keyword">var</span> net = <span class="hljs-built_in">require</span>(<span class="hljs-string">'net'</span>);
<span class="hljs-keyword">var</span> url = <span class="hljs-built_in">require</span>(<span class="hljs-string">'url'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">request</span><span class="hljs-params">(cReq, cRes)</span> </span>{
    <span class="hljs-keyword">var</span> u = url.parse(cReq.url);

    <span class="hljs-keyword">var</span> options = {
        hostname : u.hostname, 
        port     : u.port || <span class="hljs-number">80</span>,
        path     : u.path,       
        method     : cReq.method,
        headers     : cReq.headers
    };

    <span class="hljs-keyword">var</span> pReq = http.request(options, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(pRes)</span> </span>{
        cRes.writeHead(pRes.statusCode, pRes.headers);
        pRes.pipe(cRes);
    }).on(<span class="hljs-string">'error'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(e)</span> </span>{
        cRes.end();
    });

    cReq.pipe(pReq);
}

http.createServer().on(<span class="hljs-string">'request'</span>, request).listen(<span class="hljs-number">8888</span>, <span class="hljs-string">'0.0.0.0'</span>);
</code></pre>
								<p>以上代码运行后，会在本地 <code>8888</code> 端口开启 HTTP 代理服务，这个服务从请求报文中解析出请求 URL
									和其他必要参数，新建到服务端的请求，并把代理收到的请求转发给新建的请求，最后再把服务端响应返回给浏览器。修改浏览器的 HTTP 代理为
									<code>127.0.0.1:8888</code> 后再访问 HTTP 网站，代理可以正常工作。
								</p>
								<p>但是，使用我们这个代理服务后，HTTPS 网站完全无法访问，这是为什么呢？答案很简单，这个代理提供的是 HTTP 服务，根本没办法承载 HTTPS
									服务。那么是否把这个代理改为 HTTPS 就可以了呢？显然也不可以，因为这种代理的本质是中间人，而 HTTPS 网站的证书认证机制是中间人劫持的克星。普通的 HTTPS
									服务中，服务端不验证客户端的证书，中间人可以作为客户端与服务端成功完成 TLS 握手；但是中间人没有证书私钥，无论如何也无法伪造成服务端跟客户端建立 TLS
									连接。当然如果你拥有证书私钥，代理证书对应的 HTTPS 网站当然就没问题了。</p>
								<p>HTTP 抓包神器 Fiddler 的工作原理也是在本地开启 HTTP 代理服务，通过让浏览器流量走这个代理，从而实现显示和修改 HTTP 包的功能。如果要让
									Fiddler 解密 HTTPS 包的内容，需要先将它自带的根证书导入到系统受信任的根证书列表中。一旦完成这一步，浏览器就会信任 Fiddler
									后续的「伪造证书」，从而在浏览器和 Fiddler、Fiddler 和服务端之间都能成功建立 TLS 连接。而对于 Fiddler 这个节点来说，两端的 TLS
									流量都是可以解密的。</p>
								<p>如果我们不导入根证书，Fiddler 的 HTTP 代理还能代理 HTTPS 流量么？实践证明，不导入根证书，Fiddler 只是无法解密 HTTPS 流量，HTTPS
									网站还是可以正常访问。这是如何做到的，这些 HTTPS 流量是否安全呢？这些问题将在下一节揭晓。</p>
								<h3 id="toc-1">隧道代理</h3>
								<p>第二种 Web 代理的原理也很简单：</p>
								<blockquote>
									<p>HTTP 客户端通过 CONNECT 方法请求隧道代理创建一条到达任意目的服务器和端口的 TCP 连接，并对客户端和服务器之间的后继数据进行盲转发。</p>
								</blockquote>
								<p>下面这张图片同样来自于《HTTP 权威指南》，直观地展示了上述行为：</p>
								<p><img alt="web_tunnel"
										src="https://st.imququ.com/i/webp/static/uploads/2015/11/web_tunnel.png.webp"
										itemprop="image" width="650" height="551"></p>
								<p>假如我通过代理访问 A 网站，浏览器首先通过 CONNECT 请求，让代理创建一条到 A 网站的 TCP 连接；一旦 TCP
									连接建好，代理无脑转发后续流量即可。所以这种代理，理论上适用于任意基于 TCP 的应用层协议，HTTPS 网站使用的 TLS
									协议当然也可以。这也是这种代理为什么被称为隧道的原因。对于 HTTPS 来说，客户端透过代理直接跟服务端进行 TLS
									握手协商密钥，所以依然是安全的，下图中的抓包信息显示了这种场景：</p>
								<p><img alt="wireshark_connect"
										src="https://st.imququ.com/i/webp/static/uploads/2015/11/wireshark_connect.png.webp"
										itemprop="image" width="800" height="560"></p>
								<p>可以看到，浏览器与代理进行 TCP 握手之后，发起了 CONNECT 请求，报文起始行如下：</p>
								<pre><code>CONNECT imququ.com:443 HTTP/1.1
</code></pre>
								<p>对于 CONNECT 请求来说，只是用来让代理创建 TCP 连接，所以只需要提供服务器域名及端口即可，并不需要具体的资源路径。代理收到这样的请求后，需要与服务端建立
									TCP 连接，并响应给浏览器这样一个 HTTP 报文：</p>
								<pre><code>HTTP/1.1 200 Connection Established
</code></pre>
								<p>浏览器收到了这个响应报文，就可以认为到服务端的 TCP 连接已经打通，后续直接往这个 TCP 连接写协议数据即可。通过 Wireshark 的 Follow TCP
									Steam 功能，可以清楚地看到浏览器和代理之间的数据传递：</p>
								<p><img alt="wireshark_connect_detail"
										src="https://st.imququ.com/i/webp/static/uploads/2015/11/wireshark_connect_detail.png.webp"
										itemprop="image" width="800" height="555"></p>
								<p>可以看到，浏览器建立到服务端 TCP 连接产生的 HTTP 往返，完全是明文，这也是为什么 CONNECT 请求只需要提供域名和端口：如果发送了完整 URL、Cookie
									等信息，会被中间人一览无余，降低了 HTTPS 的安全性。HTTP 代理承载的 HTTPS 流量，应用数据要等到 TLS 握手成功之后通过 Application
									Data 协议传输，中间节点无法得知用于流量加密的 master-secret，无法解密数据。而 CONNECT 暴露的域名和端口，对于普通的 HTTPS
									请求来说，中间人一样可以拿到（IP 和端口很容易拿到，请求的域名可以通过 DNS Query 或者 TLS Client Hello 中的 Server Name
									Indication 拿到），所以这种方式并没有增加不安全性。</p>
								<p>了解完原理后，再用 Node.js 实现一个支持 CONNECT 的代理也很简单。核心代码如下：</p>
								<pre><b class="name">JS</b><code class="lang-js"><span class="hljs-keyword">var</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);
<span class="hljs-keyword">var</span> net = <span class="hljs-built_in">require</span>(<span class="hljs-string">'net'</span>);
<span class="hljs-keyword">var</span> url = <span class="hljs-built_in">require</span>(<span class="hljs-string">'url'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connect</span><span class="hljs-params">(cReq, cSock)</span> </span>{
    <span class="hljs-keyword">var</span> u = url.parse(<span class="hljs-string">'http://'</span> + cReq.url);

    <span class="hljs-keyword">var</span> pSock = net.connect(u.port, u.hostname, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
        cSock.write(<span class="hljs-string">'HTTP/1.1 200 Connection Established\r\n\r\n'</span>);
        pSock.pipe(cSock);
    }).on(<span class="hljs-string">'error'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(e)</span> </span>{
        cSock.end();
    });

    cSock.pipe(pSock);
}

http.createServer().on(<span class="hljs-string">'connect'</span>, connect).listen(<span class="hljs-number">8888</span>, <span class="hljs-string">'0.0.0.0'</span>);
</code></pre>
								<p>以上代码运行后，会在本地 <code>8888</code> 端口开启 HTTP 代理服务，这个服务从 CONNECT 请求报文中解析出域名和端口，创建到服务端的 TCP
									连接，并和 CONNECT 请求中的 TCP 连接串起来，最后再响应一个 Connection Established 响应。修改浏览器的 HTTP 代理为
									<code>127.0.0.1:8888</code> 后再访问 HTTPS 网站，代理可以正常工作。
								</p>
								<p>最后，将两种代理的实现代码合二为一，就可以得到全功能的 Proxy 程序了，全部代码在 50 行以内（当然异常什么的基本没考虑，这是我博客代码的一贯风格）：</p>
								<pre><b class="name">JS</b><code class="lang-js"><span class="hljs-keyword">var</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);
<span class="hljs-keyword">var</span> net = <span class="hljs-built_in">require</span>(<span class="hljs-string">'net'</span>);
<span class="hljs-keyword">var</span> url = <span class="hljs-built_in">require</span>(<span class="hljs-string">'url'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">request</span><span class="hljs-params">(cReq, cRes)</span> </span>{
    <span class="hljs-keyword">var</span> u = url.parse(cReq.url);

    <span class="hljs-keyword">var</span> options = {
        hostname : u.hostname, 
        port     : u.port || <span class="hljs-number">80</span>,
        path     : u.path,       
        method     : cReq.method,
        headers     : cReq.headers
    };

    <span class="hljs-keyword">var</span> pReq = http.request(options, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(pRes)</span> </span>{
        cRes.writeHead(pRes.statusCode, pRes.headers);
        pRes.pipe(cRes);
    }).on(<span class="hljs-string">'error'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(e)</span> </span>{
        cRes.end();
    });

    cReq.pipe(pReq);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connect</span><span class="hljs-params">(cReq, cSock)</span> </span>{
    <span class="hljs-keyword">var</span> u = url.parse(<span class="hljs-string">'http://'</span> + cReq.url);

    <span class="hljs-keyword">var</span> pSock = net.connect(u.port, u.hostname, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
        cSock.write(<span class="hljs-string">'HTTP/1.1 200 Connection Established\r\n\r\n'</span>);
        pSock.pipe(cSock);
    }).on(<span class="hljs-string">'error'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(e)</span> </span>{
        cSock.end();
    });

    cSock.pipe(pSock);
}

http.createServer()
    .on(<span class="hljs-string">'request'</span>, request)
    .on(<span class="hljs-string">'connect'</span>, connect)
    .listen(<span class="hljs-number">8888</span>, <span class="hljs-string">'0.0.0.0'</span>);
</code></pre>
								<p>需要注意的是，大部分浏览器显式配置了代理之后，只会让 HTTPS 网站走隧道代理，这是因为建立隧道需要耗费一次往返，能不用就尽量不用。但这并不代表 HTTP
									请求不能走隧道代理，我们用 Node.js 写段程序验证下（先运行前面的代理服务）：</p>
								<pre><b class="name">JS</b><code class="lang-js"><span class="hljs-keyword">var</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);

<span class="hljs-keyword">var</span> options = {
    hostname : <span class="hljs-string">'127.0.0.1'</span>,
    port     : <span class="hljs-number">8888</span>,
    path     : <span class="hljs-string">'imququ.com:80'</span>,
    method     : <span class="hljs-string">'CONNECT'</span>
};

<span class="hljs-keyword">var</span> req = http.request(options);

req.on(<span class="hljs-string">'connect'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(res, socket)</span> </span>{
    socket.write(<span class="hljs-string">'GET / HTTP/1.1\r\n'</span> +
                 <span class="hljs-string">'Host: imququ.com\r\n'</span> +
                 <span class="hljs-string">'Connection: Close\r\n'</span> +
                 <span class="hljs-string">'\r\n'</span>);

    socket.on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(chunk)</span> </span>{
        <span class="hljs-built_in">console</span>.log(chunk.toString());
    });

    socket.on(<span class="hljs-string">'end'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'socket end.'</span>);
    });
});

req.end();
</code></pre>
								<p>这段代码运行完，结果如下：</p>
								<pre><code>HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Thu, 19 Nov 2015 15:57:47 GMT
Content-Type: text/html
Content-Length: 178
Connection: close
Location: https://imququ.com/

&lt;html&gt;
&lt;head&gt;&lt;title&gt;301 Moved Permanently&lt;/title&gt;&lt;/head&gt;
&lt;body bgcolor="white"&gt;
&lt;center&gt;&lt;h1&gt;301 Moved Permanently&lt;/h1&gt;&lt;/center&gt;
&lt;hr&gt;&lt;center&gt;nginx&lt;/center&gt;
&lt;/body&gt;
&lt;/html&gt;

socket end.
</code></pre>
								<p>可以看到，通过 CONNECT 让代理打开到目标服务器的 TCP 连接，用来承载 HTTP 流量也是完全没问题的。</p>
								<p>最后，HTTP
									的认证机制可以跟代理配合使用，使得必须输入正确的用户名和密码才能使用代理，这部分内容比较简单，这里略过。在本文第二部分，我打算谈谈如何把今天实现的代理改造为 HTTPS
									代理，也就是如何让浏览器与代理之间的流量走 HTTPS 安全机制。注：已经写完了，<a
										href="https://imququ.com/post/web-proxy-2.html">点这里查看</a>。</p>
								<p> 本文链接：<a rel="bookmark" title="Permalink to HTTP 代理原理及实现（一）"
										href="https://imququ.com/post/web-proxy.html">https://imququ.com/post/web-proxy.html</a>，<a
										href="https://imququ.com/post/web-proxy.html#comments">参与评论 »</a></p>
								<p>--<acronym title="End of File">EOF</acronym>--</p>
								<p class="post-info"> 发表于&nbsp;<span class="date">2015-11-20 00:45:24</span> ，并被添加「 <a
										href="https://imququ.com/search.html?s=tag%3ANode">Node</a> 、 <a
										href="https://imququ.com/search.html?s=tag%3AProxy">Proxy</a> 、 <a
										href="https://imququ.com/search.html?s=tag%3AHTTP">HTTP</a> 」标签 。<a
										href="https://imququ.com/post/web-proxy.md">查看本文 Markdown 版本 »</a></p>
								<p class="copyright-info">本站使用「<a
										href="http://creativecommons.org/licenses/by/4.0/deed.zh">署名 4.0
										国际</a>」创作共享协议，<a href="https://imququ.com/post/about.html#toc-1">相关说明 »</a></p>
								<p class="expired-tips"> 提醒：本文最后更新于&nbsp;2403&nbsp;天前，文中所描述的信息可能已发生改变，请谨慎使用。</p>
								<div class="entry-series">
									<h3>专题「HTTP 相关」的其他文章&nbsp;<a href="https://imququ.com/series.html#toc-7"
											title="更多">»</a></h3>
									<ul>
										<li><a href="https://imququ.com/post/http-alt-svc.html">HTTP Alternative
												Services 介绍</a>&nbsp;<span class="date">(Aug 21, 2016)</span> </li>
										<li><a href="https://imququ.com/post/sth-about-switch-to-https-3.html">关于启用
												HTTPS 的一些经验分享（三）</a>&nbsp;<span class="date">(May 05, 2016)</span> </li>
										<li><a href="https://imququ.com/post/how-to-compress-http-request-body.html">如何压缩
												HTTP 请求正文</a>&nbsp;<span class="date">(Apr 18, 2016)</span> </li>
										<li><a href="https://imququ.com/post/content-encoding-header-in-http.html">HTTP
												协议中的 Content-Encoding</a>&nbsp;<span class="date">(Apr 17, 2016)</span>
										</li>
										<li><a href="https://imququ.com/post/how-to-decrypt-https.html">三种解密 HTTPS
												流量的方法介绍</a>&nbsp;<span class="date">(Mar 28, 2016)</span> </li>
										<li><a href="https://imququ.com/post/http-public-key-pinning.html">HTTP Public
												Key Pinning 介绍</a>&nbsp;<span class="date">(Mar 05, 2016)</span> </li>
										<li><a href="https://imququ.com/post/sth-about-switch-to-https-2.html">关于启用
												HTTPS 的一些经验分享（二）</a>&nbsp;<span class="date">(Dec 22, 2015)</span> </li>
										<li><a href="https://imququ.com/post/sth-about-switch-to-https.html">关于启用 HTTPS
												的一些经验分享（一）</a>&nbsp;<span class="date">(Dec 04, 2015)</span> </li>
										<li><a href="https://imququ.com/post/web-proxy-2.html">HTTP
												代理原理及实现（二）</a>&nbsp;<span class="date">(Nov 20, 2015)</span> </li>
										<li><a href="https://imququ.com/post/content-security-policy-level-2.html">Content
												Security Policy Level 2 介绍</a>&nbsp;<span class="date">(Oct 05,
												2015)</span> </li>
									</ul>
								</div>
							</div>
						</article>
						<nav class="page-navi"> <a href="https://imququ.com/post/golomb-coded-sets.html"
								class="prev">«&nbsp;Golomb-coded sets 原理介绍</a> <a
								href="https://imququ.com/post/web-proxy-2.html" class="next">HTTP 代理原理及实现（二）&nbsp;»</a>
						</nav>
						<section id="comments">
							<h1 class="title">Comments</h1>
							<div class="total_thread" data-identifier="post-web-proxy"
								data-url="https://imququ.com/post/web-proxy.html"></div>
						</section>
					</div>
				</div>
				<footer id="footer" class="inner"> ©&nbsp;2022&nbsp;-&nbsp;JerryQu 的小站&nbsp;-&nbsp;<a target="_blank"
						rel="nofollow noopener" class="external beian"
						href="http://www.beian.miit.gov.cn/">京ICP备15046275号-1</a><br> Powered by&nbsp;<a
						href="https://imququ.com/post/readme.html">ThinkJS 3</a>&nbsp;&amp;&nbsp;<a target="_blank"
						rel="nofollow noopener" class="external"
						href="https://github.com/shashankmehta/greyshade">GreyShade</a></footer>
			</div>
		</div> <input type="hidden" id="CURRENT_PAGE" value="post-web-proxy"> <input type="hidden" id="CDN_DOMAIN"
			value="https://st.imququ.com">
	</body>
</html>
